package com.qwert.shortlink.project.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.qwert.shortlink.project.common.convention.exception.ServiceException;
import com.qwert.shortlink.project.dao.entity.ShortLink;
import com.qwert.shortlink.project.dao.entity.ShortLinkRouter;
import com.qwert.shortlink.project.dao.mapper.ShortLinkMapper;
import com.qwert.shortlink.project.dao.mapper.ShortLinkRouterMapper;
import com.qwert.shortlink.project.service.RedirectLinkService;
import com.qwert.shortlink.project.toolkit.LinkUtil;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.Date;
import java.util.concurrent.TimeUnit;

import static com.qwert.shortlink.project.common.constant.RedisKeyConstant.*;

/**
 * @author qwert
 * @version 1.0.0
 * @create 2024/7/5 18:15
 **/
@Service
@RequiredArgsConstructor
public class RedirectLinkServiceImpl implements RedirectLinkService {

    private final ShortLinkRouterMapper shortLinkRouterMapper;
    private final ShortLinkMapper shortLinkMapper;
    private final StringRedisTemplate stringRedisTemplate;
    private final RedissonClient redissonClient;
    private final RBloomFilter<String> shortLinkCreateCachePenetrationBloomFilter;


    @Override
    public void redirectLink(String shortLinkUri, ServletRequest request, ServletResponse response) throws IOException {
        String fullShortUrl = "nurk.ink/" + shortLinkUri;
        // 将短链接提取
        String originalUrl = stringRedisTemplate.opsForValue().get(String.format(ROUTER_SHORT_LINK_KEY, fullShortUrl));
        if (StrUtil.isNotBlank(originalUrl)) {
            // TODO ? 如何访问成功，重置缓存过期时间
            ((HttpServletResponse) response).sendRedirect(originalUrl);
            return;
        }

        // 过滤本身不存在的短链接
        // TODO此处判断短链接是否存在是用六位短链接判断的。
        boolean contains = shortLinkCreateCachePenetrationBloomFilter.contains(fullShortUrl);
        if (!contains) {
//            throw new ServiceException(LinkErrorCode.LINK_NULL_ERROR);
            ((HttpServletResponse) response).sendRedirect("/page/notfound");
            return;
        }

        // 过滤掉误判，及可能删除的短链接（即缓存为空，查询缓存）
        String routerNullUrl = stringRedisTemplate.opsForValue().get(String.format(ROUTER_IS_NOT_SHORT_LINK_KEY, fullShortUrl));
        if(StrUtil.isNotBlank(routerNullUrl)){
//            throw new ServiceException(LinkErrorCode.LINK_INVALID_ERROR);
            ((HttpServletResponse) response).sendRedirect("/page/notfound");
            return;
        }

        // 短链接有效缓存为空，进行数据库查询
        // 获取短链接分布式锁
        RLock lock = redissonClient.getLock(String.format(LOCK_ROUTER_SHORT_LINK_KEY, fullShortUrl));
        lock.lock();
        try {
            originalUrl = stringRedisTemplate.opsForValue().get(String.format(ROUTER_SHORT_LINK_KEY, fullShortUrl));
            // 双判定锁
            if (StrUtil.isNotBlank(originalUrl)) {
                ((HttpServletResponse) response).sendRedirect(originalUrl);
                return;
            }
            // 查询数据库
            // 通过路由表查询对应的gid
            LambdaQueryWrapper<ShortLinkRouter> routerQueryWrapper = Wrappers.lambdaQuery(ShortLinkRouter.class)
                    .eq(ShortLinkRouter::getFullShortUrl, fullShortUrl);
            ShortLinkRouter shortLinkRouter = shortLinkRouterMapper.selectOne(routerQueryWrapper);
            if (shortLinkRouter == null) {
                // 短链接路由表为空，当前短链接无效，添加空缓存（空缓存和有效缓存是两个不同的缓存）
                stringRedisTemplate.opsForValue().set(String.format(ROUTER_IS_NOT_SHORT_LINK_KEY, fullShortUrl), "-",30, TimeUnit.MINUTES);
//                throw new ServiceException(LinkErrorCode.LINK_ROUTER_NULL_ERROR);
                ((HttpServletResponse) response).sendRedirect("/page/notfound");
                return;

            }

            // 通过gid和原始链接查询原始链接
            LambdaQueryWrapper<ShortLink> shortLinkQueryWrapper = Wrappers.lambdaQuery(ShortLink.class)
                    .eq(ShortLink::getGid, shortLinkRouter.getGid())
                    .eq(ShortLink::getFullShortUrl, fullShortUrl)
                    .eq(ShortLink::getDelFlag, 0)
                    .eq(ShortLink::getEnableStatus, 0);
            ShortLink shortLink = shortLinkMapper.selectOne(shortLinkQueryWrapper);
            if(shortLink == null || StrUtil.isBlank(shortLink.getOriginUrl())){
                stringRedisTemplate.opsForValue().set(String.format(ROUTER_IS_NOT_SHORT_LINK_KEY, fullShortUrl), "-",30, TimeUnit.MINUTES);
//                throw new ServiceException(LinkErrorCode.LINK_SHORT_LINK_NULL_ERROR);
                ((HttpServletResponse) response).sendRedirect("/page/notfound");
                return;

            }
            // 判断当前链接是否有效，无效则进行删除
            if(shortLink.getValidDate() != null && shortLink.getValidDate().before(new Date())) {
                //TODO 有效期无效或者已经过期，进行删除操作（交给其他线程操作）
                stringRedisTemplate.opsForValue().set(String.format(ROUTER_IS_NOT_SHORT_LINK_KEY, fullShortUrl), "-",30, TimeUnit.MINUTES);
                return;
            }
            // 将查询的数据存放到redis，并且重定向原始链接
            stringRedisTemplate.opsForValue().set(String.format(ROUTER_SHORT_LINK_KEY, fullShortUrl),
                    shortLink.getOriginUrl(),
                    LinkUtil.getLinkCacheValidTime(shortLink.getValidDate()),
                    TimeUnit.MILLISECONDS
            );
            ((HttpServletResponse) response).sendRedirect(originalUrl);
        } catch (ServiceException e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }
    }

    @SneakyThrows
    @Override
    public String getTitleByUrl(String aimUrl) {
        Document documented = Jsoup.connect(aimUrl).get();
        return documented.title();
    }
}
